﻿using System;

namespace NhanDang
{
    class PatternRecognition
    {
        #region Variables
        private int numberInput;
        private int numberHidden;
        private int numberOutput;
        private double n; //Hệ số học
        private double[] arrDataInput; //Dữ liệu đưa vào
        private double[,] arrWInput; //mảng lưu trọng số giữa lớp đầu vào và lớp ẩn
        private double[,] arrWOutput; //mảng lưu trọng số giữa lớp ẩn và lớp ra
        private double[] arrDataOutput; //mảng lưu dữ liệu đầu ra
        private double[] arrValueHidden; // mảng giá trị lớp ẩn
        private double[] arrOutputError; // mảng lưu sai số của lớp ra
        private double[] arrHiddenError; //mảng lưu sai số của lớp ẩn
        private double[] arrDesiredOuput; //mảng dùng để lưu đầu ra đúng hoặc giá trị đầu ra đã được học
        #endregion

        #region Properties
        //Dữ liệu đưa vào
        public double[] ArrDataInput
        {
            get
            {
                return arrDataInput;
            }
            set
            {
                this.arrDataInput = value;
            }
        }

        //mảng lưu trọng số giữa lớp đầu vào và lớp ẩn
        public double[,] ArrWInput
        {
            get
            {
                return arrWInput;
            }
            set
            {
                this.arrWInput = value;
            }
        }

        //mảng lưu trọng số giữa lớp ẩn và lớp ra
        public double[,] ArrWOutput
        {
            get
            {
                return arrWOutput;
            }
            set
            {
                this.arrWOutput = value;
            }
        }

        //mảng dùng để lưu đầu ra đúng hoặc giá trị đầu ra đã được học
        public double[] ArrDesiredOuput
        {
            get
            {
                return arrDesiredOuput;
            }
            set
            {
                this.arrDesiredOuput = value;
            }
        }
        #endregion

        #region Contructor
        public PatternRecognition(int numberInput, int numberHidden, int numberOutput, double n) //double n; //Hệ số học
        {
            this.numberInput = numberInput;
            this.numberHidden = numberHidden;
            this.numberOutput = numberOutput;
            this.arrDataInput = new double[numberInput]; 
            this.arrWInput = new double[numberInput, numberHidden]; 
            this.arrWOutput = new double[numberHidden, numberOutput];
            this.arrDataOutput = new double[numberOutput];
            this.arrValueHidden = new double[numberHidden];
            this.arrOutputError = new double[numberOutput];
            this.arrHiddenError = new double[numberHidden];
            this.arrDesiredOuput = new double[numberOutput]; 
            this.n = n;
            InitializeW(); //Khởi tạo trọng số ngẫu nhiên
        }

        /// <summary>
        /// Khởi tạo lớp không có trọng số lớp ẩn và lớp ra
        /// </summary>
        /// <param name="numberInput">Số phần tử lớp Input</param>
        /// <param name="numberHidden">Số phần tử lớp ẩn</param>
        /// <param name="numberOutput">Số phần tử lớp ra</param>
        /// <param name="arrDataInput">Mảng lưu 1 mẫu học</param>
        /// <param name="arrDesiredOuput">Mảng lưu đầu ra đúng của mẫu</param>
        public PatternRecognition(int numberInput, int numberHidden, int numberOutput, double[] arrDataInput, double[] arrDesiredOuput, double n)
        {
            this.numberInput = numberInput;
            this.numberHidden = numberHidden;
            this.numberOutput = numberOutput;
            this.arrDataInput = arrDataInput; 
            this.arrWInput = new double[numberInput, numberHidden];
            this.arrWOutput = new double[numberHidden, numberOutput];
            this.arrDataOutput = new double[numberOutput];
            this.arrValueHidden = new double[numberHidden];
            this.arrOutputError = new double[numberOutput];
            this.arrHiddenError = new double[numberHidden];
            this.arrDesiredOuput = arrDesiredOuput;
            this.n = n;
            InitializeW(); //Khởi tạo trọng số ngẫu nhiên
        }

        /// <summary>
        /// Khởi tạo lớp không có trọng số lớp ẩn và lớp ra
        /// </summary>
        /// <param name="numberInput">Số phần tử lớp Input</param>
        /// <param name="numberHidden">Số phần tử lớp ẩn</param>
        /// <param name="numberOutput">Số phần tử lớp ra</param>
        /// <param name="arrDataInput">Mảng chứa 1 mẫu học</param>
        /// <param name="arrDesiredOuput">Mảng chứa đầu ra đúng của mẫu</param>
        /// <param name="arrWInput">Mảng chứa bộ trọng số giữa lớp vào và lớp ẩn được học trước đó</param>
        /// <param name="arrWOutput">Mảng chứa bộ trọng số giữa lớp ẩn và lớp ra đưuọc học trước đó</param>
        public PatternRecognition(int numberInput, int numberHidden, int numberOutput, double[] arrDataInput, double[] arrDesiredOuput, double[,] arrWInput, double[,] arrWOutput, double n)
        {
            this.numberInput = numberInput;
            this.numberHidden = numberHidden;
            this.numberOutput = numberOutput;
            this.arrDataInput = arrDataInput;
            this.arrWInput = arrWInput;
            this.arrWOutput = arrWOutput;
            this.arrDataOutput = new double[numberOutput];
            this.arrValueHidden = new double[numberHidden];
            this.arrOutputError = new double[numberOutput];
            this.arrHiddenError = new double[numberHidden];
            this.arrDesiredOuput = arrDesiredOuput;
            this.n = n;
        }

        /// <summary>
        /// Hàm khởi tạo các trọng số ngẫu nhiên
        /// </summary>
        public void InitializeW()
        {
            Random ra = new Random();
            for (int i = 0; i < numberInput; i++)
            {
                for (int j = 0; j < numberHidden; j++)
                {
                    arrWInput[i, j] = ra.NextDouble() * (ra.Next(0, 2) == 1 ? -1 : 1);
                }
            }
            for (int i = 0; i < numberHidden; i++)
            {
                for (int j = 0; j < numberOutput; j++)
                {
                    arrWOutput[i, j] = ra.NextDouble() * (ra.Next(0, 2) == 1 ? -1 : 1);
                }
            }
        }
        #endregion

        #region Methods
        public void CalculatedValueHidden()//Tính giá trị xuất của nút ẩn
        {
            for (int i = 0; i < numberHidden; i++)//numberHidden: so phan tu lop an............
            {
                double num = 0;
                for (int j = 0; j < numberInput; j++)//..........: so phan tu lop vao=.......
                {
                    num += arrDataInput[j] * arrWInput[j, i];// +=mang luu trong so giua lop an vs lop vao * du lieu dua vao
                }
                arrValueHidden[i] = 1.0 / (1.0 + Math.Exp(-num));//manggia tri lop an= 
            }
        }
        public void CalculatedValueOutput()//Tính giá trị xuất cho nút xuất
        {
            for (int i = 0; i < numberOutput; i++)//.........: so phan tu lop ra..............
            {
                double num = 0;
                for (int j = 0; j < numberHidden; j++)
                {
                    num += arrValueHidden[j] * arrWOutput[j, i];//= mang gia tri lop an * mang luu trong so giua lop an vs lop ra
                }
                arrDataOutput[i] = 1.0 / (1.0 + Math.Exp(-num));//mang luu giu lieu dau ra, Exp: e^(-num)
            }
        }
        public void CalculatedErrors()// Tính toán sai lệch
        {
            for (int i = 0; i < numberOutput; i++)// sai lệch của neural lớp ra
            {
                arrOutputError[i] = arrDataOutput[i] * (1 - arrDataOutput[i]) * (arrDesiredOuput[i] - arrDataOutput[i]);
            }
            for (int i = 0; i < numberHidden; i++)// sai lệch của noeural lớp ẩn
            {
                double deltal = 0;
                for (int j = 0; j < numberOutput; j++)
                {
                    deltal += arrWOutput[i, j] * arrOutputError[j];
                }
                arrHiddenError[i] = arrValueHidden[i] * (1 - arrValueHidden[i]) * deltal;
            }
        }
        public void UpdateNewWeights()
        {
            for (int i = 0; i < numberHidden; i++)//Cập nhật trọng số ở neural đầu ra
            {
                for (int j = 0; j < numberOutput; j++)
                {
                    double deltaWeight = n * arrOutputError[j] * arrValueHidden[i];
                    arrWOutput[i, j] += deltaWeight;
                }
            }
            for (int i = 0; i < numberInput; i++)//Cập nhật trọng số ở neural ẩn
            {
                for (int j = 0; j < numberHidden; j++)
                {
                    double deltaWeight = n * arrHiddenError[j] * arrDataInput[i];
                    arrWInput[i, j] += deltaWeight;
                }
            }
        }

        public void NeuralLearning(System.Windows.Forms.ProgressBar pr)
        {
            string fileName = "";
            for (int i = 0; i < arrDesiredOuput.Length; i++)//arrDesiredOuput : mảng dùng để lưu đầu ra đúng hoặc giá trị đầu ra đã được học
            {
                fileName += arrDesiredOuput[i].ToString();
            }
            int count = 0;
            while (count++ < 10000)
            {
                pr.Value = count;
                CalculatedValueHidden();//tinh gia tri xuat cua nut an
                CalculatedValueOutput();//tinh gia tri xuat cua nut xuát
                CalculatedErrors();// Tính toán sai lệch
                UpdateNewWeights();//cap nhat trong so
            }
            //lay cac thong so tu tren xuong
            DataProvider.OverWriteArrayToFile(fileName, arrWInput, numberInput, numberHidden);
            DataProvider.WriteArrayToFile(fileName, arrWOutput, numberHidden, numberOutput);
            DataProvider.WriteArrayToFile(fileName, arrDataOutput);
        }

        public double FindNumberError()
        {
            CalculatedValueHidden();//tinh gia tri xuat cua nut an
            CalculatedValueOutput();//tinh gia tri xuat cua nut xuát
            double result = 0;
            for (int i = 0; i < numberOutput; i++)
            {
                result += Math.Abs(arrDesiredOuput[i] - arrDataOutput[i]) * Math.Abs(arrDesiredOuput[i] - arrDataOutput[i]);
            }
            return result;
        }
        #endregion
    }
}
